using codeparser.net.Exceptions;

using System;
using System.Collections.Generic;

namespace codeparser.net
{
    /// <summary>
    /// Represents the parser.
    /// </summary>
    public class Parser
    {
        /// <summary>
        /// Contains the parse configuration.
        /// </summary>
        private ParserConfiguration _configuration;

        /// <summary>
        /// Initializes a new instance of the <see cref="Parser"/> class.
        /// </summary>
        /// <param name="configuration">The configuration.</param>
        public Parser(ParserConfiguration configuration)
        {
            this._configuration = configuration;
        }

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        /// <value>The configuration.</value>
        public ParserConfiguration Configuration
        {
            get
            {
                return this._configuration;
            }
        }

        /// <summary>
        /// Parses the specified text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns>The parsed text.</returns>
        public string Parse(string text)
        {
            // Validate the text parameter.
            if (text == null)
            {
                throw new ArgumentNullException("text");
            }

            // If no text is to be parsed, return an empty string.
            if (text == "")
            {
                return "";
            }

            // Build the syntax tree.
            Node root = BuildTree(text);

            // Get the parsed text from the syntax tree.
            string parsedText = root.ToString(this);

            // Return the parsed text to the caller.
            return parsedText;
        }

        private Node BuildTree(string text)
        {
            // Set up the level and the stack.
            int level = 0;
            Stack<Tag> tagStack = new Stack<Tag>();

            // Set up the root node.
            Node root = new Node();

            // The current node points to the root node in the beginning.
            Node currentNode = root;

            // Iterate over the text.
            for (int currentPosition = 0; currentPosition < text.Length; currentPosition++)
            {
                // Find the next left bracket and get any text up to that bracket.
                int leftPosition = text.IndexOf("[", currentPosition);
                if (leftPosition == -1)
                {
                    currentNode.Tokens.Add(new Text(text.Substring(currentPosition)));
                    break;
                }
                currentNode.Tokens.Add(new Text(text.Substring(currentPosition, leftPosition - currentPosition)));

                // Check whether the next character is a left bracket, too.
                if (leftPosition + 1 < text.Length && text[leftPosition + 1] == '[')
                {
                    currentNode.Tokens.Add(new Text("[", true));
                    currentPosition = leftPosition + 1;
                    continue;
                }

                // Find the right bracket.
                int rightPosition = text.IndexOf("]", leftPosition);
                if (rightPosition == -1)
                {
                    break;
                }

                // Get the tag.
                Tag tag = new Tag(text.Substring(leftPosition + 1, rightPosition - leftPosition - 1));

                // Check whether the tag is a valid tag. Use this loop to check also, whether the
                // tag is a single tag.
                bool isValidTag = false;
                bool isSingleTag = false;
                foreach (TagConfiguration tagConfiguration in this._configuration.TagConfigurations)
                {
                    if (tagConfiguration.Tag == tag.Name)
                    {
                        isSingleTag = tagConfiguration.IsSingleTag;
                        isValidTag = true;
                        break;
                    }
                }
                if (!isValidTag)
                {
                    TagConfiguration tagConfiguration = this._configuration.GetTagConfiguration(tag.Name);
                    isSingleTag = tagConfiguration.IsSingleTag;
                    isValidTag = true;
                }
                if (!isValidTag)
                {
                    throw new InvalidTagException(tag.Name);
                }

                // If it is an opening tag, increase the level by one and push it onto the stack.
                if (!tag.IsClosingTag)
                {
                    level++;
                    tagStack.Push(tag);

                    Node childNode = new Node(currentNode);
                    currentNode.Tokens.Add(childNode);
                    currentNode = childNode;
                }

                // Add the tag to the current node.
                currentNode.Tokens.Add(tag);

                // If the tag is an opening tag, check whether its content shall be parsed.
                if (!tag.IsClosingTag && !this._configuration.GetTagConfiguration(tag.Name).ParseContent)
                {
                    // Move to the beginning of the text.
                    currentPosition = rightPosition + 1;

                    // Get the contained text.
                    leftPosition = text.IndexOf("[/" + tag.Name, currentPosition);
                    if (leftPosition == -1)
                    {
                        break;
                    }
                    currentNode.Tokens.Add(new Text(text.Substring(rightPosition + 1, leftPosition - currentPosition)));

                    // Get the closing tag.
                    rightPosition = text.IndexOf("]", leftPosition);
                    if (rightPosition == -1)
                    {
                        break;
                    }

                    // Get the tag.
                    tag = new Tag(text.Substring(leftPosition + 1, rightPosition - leftPosition - 1));

                    // Add the tag to the current node.
                    currentNode.Tokens.Add(tag);
                }

                // If the tag is a closing tag, decrease the level by one and try to pop the tag
                // off the stack. If this fails, throw an invalid nesting exception.
                if (tag.IsClosingTag)
                {
                    level--;
                    if (tagStack.Peek().Name != tag.Name)
                    {
                        throw new InvalidTagNestingException();
                    }
                    tagStack.Pop();

                    currentNode = currentNode.Parent;
                }

                // If the tag is a single tag we must decrease the level by one and pop the tag
                // manually off the stack.
                if (isSingleTag)
                {
                    level--;
                    tagStack.Pop();

                    currentNode = currentNode.Parent;
                }

                // Proceed behind the tag.
                currentPosition = rightPosition;
            }

            // If there are tags left, there has been an invalid nesting.
            if (tagStack.Count > 0)
            {
                throw new InvalidTagNestingException();
            }

            // Return the root node to the caller.
            return root;
        }
    }
}